/* 
 * Copyright 2011 VelocitiSoftware
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.velociti.ikarus.ui.widget;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.vaadin.data.Container;
import com.vaadin.data.Item;
import com.vaadin.data.Property;
import com.vaadin.data.util.IndexedContainer;
import com.vaadin.terminal.PaintException;
import com.vaadin.terminal.PaintTarget;
import com.vaadin.ui.AbstractField;
import com.velociti.ikarus.widget.client.ui.VIkarusBTextField;

/** ######IkarusBTextField#####
 * -ButtonTextfield converts the entered text to a button when it faces 
 *  an input character of value textDelimiter.
 * -ButtonTextField aims to communicate with server minimum.
 * -ButtonTextField can expand its height to the given maxLinestoExpand value (Default:3) 
 *  if there are more lines than that value, it will show vertical scroll bar.
 * -ButtonTextField has focus remember functionality, After server communication, 
 *  it remembers the next focus component to focus.
 * -ButtonTextField has ItemContainer behaviour, each button is represented as item
 *  with button title as the itemId. One can register ItemSetChangeListener to get notified 
 *  about the changes in dataset.
 * -REMARK: Browsers backspace functionality must be disabled.
 *  
 * @author Alper Turkyilmaz - VelocitiSoftware - 2011
 * @version 1.0.1
 * 
 */

@com.vaadin.ui.ClientWidget(VIkarusBTextField.class)
public class IkarusBTextField extends AbstractField implements Container, Container.PropertySetChangeListener, Container.PropertySetChangeNotifier,
		Container.Viewer, Container.ItemSetChangeNotifier, Container.ItemSetChangeListener {
	// default line height
	private final static int DEFAULT_HEIGHT = 22;
	// default delimiter
	private final static char DEFAULT_DELIMITER = ' ';

	// flag inorder to be awareof the changes are being applied
	private boolean changingVariables = false;

	private char textDelimiter;
	private int maxLinesToExpand = 3;
	protected Container items;
	private Set<Container.ItemSetChangeListener> itemSetEventListeners = null;
	private Set<Container.PropertySetChangeListener> propertySetEventListeners = null;

	private class ItemSetChangeEvent implements Serializable, Container.ItemSetChangeEvent {
		public Container getContainer() {
			return IkarusBTextField.this;
		}
	}

	private class PropertySetChangeEvent implements Container.PropertySetChangeEvent, Serializable {
		public Container getContainer() {
			return IkarusBTextField.this;
		}

	}

	public IkarusBTextField() {
		textDelimiter = DEFAULT_DELIMITER;
		setHeight(DEFAULT_HEIGHT, UNITS_PIXELS);
		setContainerDataSource(new IndexedContainer());
	}

	public IkarusBTextField(String caption) {
		this();
		setCaption(caption);
	}

	public Class<?> getType() {
		return String.class;
	}

	public void paintContent(PaintTarget target) throws PaintException {
		super.paintContent(target);

		// send variables
		target.addVariable(this, "textDelimiter", textDelimiter);
		target.addVariable(this, "textValue", (String) getValue());
		target.addVariable(this, "maxLinesToExpand", maxLinesToExpand);

		// send buttons
		target.startTag("buttons");
		List<?> buttons = new ArrayList<Object>(items.getItemIds());
		final Iterator<?> i = buttons.iterator();
		while (i.hasNext()) {
			final Object id = i.next();
			target.startTag("so");
			target.addAttribute("key", (String) id);
			target.endTag("so");
		}
		target.endTag("buttons");
	}

	public char getTextDelimiter() {
		return textDelimiter;
	}

	/*
	 * Set the delimiter character , does not effect the items(buttons)
	 * previously constructed, it is recommended to set it once just after
	 * initialization of the widget.
	 */
	public void setTextDelimiter(char textDelimiter) {
		this.textDelimiter = textDelimiter;
		if (!changingVariables) {
			requestRepaint();
		}
	}

	public void changeVariables(Object source, Map<String, Object> variables) {
		changingVariables = true;
		try {
			super.changeVariables(source, variables);
			if (variables.containsKey("textValue")) {
				String text = (String) variables.get("textValue");
				setValue(text, true);
			}

			if (variables.containsKey("itemAdded")) {
				String itemId = (String) variables.get("itemId");
				Item item = (Item) addItem(itemId, false);
			}
			if (variables.containsKey("itemRemoved")) {
				String itemId = (String) variables.get("itemId");
				removeItem(itemId, false);
			}
		} finally {
			changingVariables = false;
		}
	}

	public void containerItemSetChange(ItemSetChangeEvent event) {
		fireItemSetChange(true);
	}

	public void addListener(ItemSetChangeListener listener) {
		if (itemSetEventListeners == null) {
			itemSetEventListeners = new LinkedHashSet<Container.ItemSetChangeListener>();
		}
		itemSetEventListeners.add(listener);

	}

	public void removeListener(ItemSetChangeListener listener) {
		if (itemSetEventListeners != null) {
			itemSetEventListeners.remove(listener);
			if (itemSetEventListeners.isEmpty()) {
				itemSetEventListeners = null;
			}
		}
	}

	public Object addItem() throws UnsupportedOperationException {
		return addItem(null, true);
	}

	Object addItem(Object itemId, boolean repaintNeeded) throws UnsupportedOperationException {
		Object retval = null;
		if (itemId == null) {
			retval = items.addItem();
		} else {
			retval = items.addItem(itemId);
		}
		if (retval != null && !(items instanceof Container.ItemSetChangeNotifier)) {
			fireItemSetChange(repaintNeeded);
		}
		return retval;
	}

	public Item addItem(Object itemId) throws UnsupportedOperationException {
		return (Item) addItem(itemId, true);
	}

	public boolean removeItem(Object itemId) throws UnsupportedOperationException {
		return removeItem(itemId, true);
	}

	boolean removeItem(Object itemId, boolean repaintNeeded) throws UnsupportedOperationException {
		final boolean retval = items.removeItem(itemId);
		if (retval && !(items instanceof Container.ItemSetChangeNotifier)) {
			fireItemSetChange(repaintNeeded);
		}
		return retval;
	}

	protected void fireItemSetChange(boolean repaintNeeded) {
		if (itemSetEventListeners != null && !itemSetEventListeners.isEmpty()) {
			final Container.ItemSetChangeEvent event = new ItemSetChangeEvent();
			final Object[] listeners = itemSetEventListeners.toArray();
			for (int i = 0; i < listeners.length; i++) {
				((Container.ItemSetChangeListener) listeners[i]).containerItemSetChange(event);
			}
		}
		if (repaintNeeded) {
			requestRepaint();
		}
	}

	public void containerItemSetChange(Container.ItemSetChangeEvent event) {
		fireItemSetChange(true);
	}

	public Item getItem(Object itemId) {
		return items.getItem(itemId);
	}

	public Collection<?> getContainerPropertyIds() {
		return items.getContainerPropertyIds();
	}

	public Collection<?> getItemIds() {
		return items.getItemIds();
	}

	public Property getContainerProperty(Object itemId, Object propertyId) {
		return items.getContainerProperty(itemId, propertyId);
	}

	public Class<?> getType(Object propertyId) {
		return items.getType(propertyId);
	}

	public int size() {
		return items.size();
	}

	public boolean containsId(Object itemId) {
		if (itemId != null) {
			return items.containsId(itemId);
		} else {
			return false;
		}
	}

	public boolean addContainerProperty(Object propertyId, Class<?> type, Object defaultValue) throws UnsupportedOperationException {
		final boolean retval = items.addContainerProperty(propertyId, type, defaultValue);
		if (retval && !(items instanceof Container.PropertySetChangeNotifier)) {
			firePropertySetChange();
		}
		return retval;
	}

	public boolean removeContainerProperty(Object propertyId) throws UnsupportedOperationException {
		final boolean retval = items.removeContainerProperty(propertyId);
		if (retval && !(items instanceof Container.PropertySetChangeNotifier)) {
			firePropertySetChange();
		}
		return retval;
	}

	public boolean removeAllItems() throws UnsupportedOperationException {
		final boolean retval = items.removeAllItems();
		if (retval) {
			setValue(null);
			if (!(items instanceof Container.ItemSetChangeNotifier)) {
				fireItemSetChange(true);
			}
		}
		return retval;
	}

	protected void firePropertySetChange() {
		if (propertySetEventListeners != null && !propertySetEventListeners.isEmpty()) {
			final Container.PropertySetChangeEvent event = new PropertySetChangeEvent();
			final Object[] listeners = propertySetEventListeners.toArray();
			for (int i = 0; i < listeners.length; i++) {
				((Container.PropertySetChangeListener) listeners[i]).containerPropertySetChange(event);
			}
		}
		requestRepaint();
	}

	public void setContainerDataSource(Container newDataSource) {
		if (newDataSource == null) {
			newDataSource = new IndexedContainer();
		}
		if (items != newDataSource) {
			if (items != null) {
				if (items instanceof Container.ItemSetChangeNotifier) {
					((Container.ItemSetChangeNotifier) items).removeListener(this);
				}
				if (items instanceof Container.PropertySetChangeNotifier) {
					((Container.PropertySetChangeNotifier) items).removeListener(this);
				}
			}
			items = newDataSource;
			if (items != null) {
				if (items instanceof Container.ItemSetChangeNotifier) {
					((Container.ItemSetChangeNotifier) items).addListener(this);
				}
				if (items instanceof Container.PropertySetChangeNotifier) {
					((Container.PropertySetChangeNotifier) items).addListener(this);
				}
			}
			setValue(null);
			requestRepaint();
		}
	}

	public Container getContainerDataSource() {
		return items;
	}

	public void addListener(PropertySetChangeListener listener) {
		if (propertySetEventListeners == null) {
			propertySetEventListeners = new LinkedHashSet<Container.PropertySetChangeListener>();
		}
		propertySetEventListeners.add(listener);
	}

	public void removeListener(PropertySetChangeListener listener) {
		if (propertySetEventListeners != null) {
			propertySetEventListeners.remove(listener);
			if (propertySetEventListeners.isEmpty()) {
				propertySetEventListeners = null;
			}
		}
	}

	public void containerPropertySetChange(Container.PropertySetChangeEvent event) {
		firePropertySetChange();
	}

	public int getMaxLinesToExpand() {
		return maxLinesToExpand;
	}

	/*
	 * Set the max number of lines that this component can expand before showing
	 * vertical scrollbar. does not effect the items(buttons) previously
	 * constructed, it is recommended to set it once just after initialization
	 * of the widget.
	 */
	public void setMaxLinesToExpand(int maxLinestoExpand) {
		this.maxLinesToExpand = maxLinestoExpand;
		if (!changingVariables) {
			requestRepaint();
		}
	}
}
